package com.googlecode.websecuritychecks;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.googlecode.websecuritychecks.model.*;
import org.w3c.dom.*;

/**
 * 
 * @author Sai Kris
 * 
 */
public abstract class RegexBasedCheck implements Check {

    private CheckFailureCallback checkFailureCallback;
    private ConcurrentHashMap<String, Pattern> regexCache = new ConcurrentHashMap<>();

    protected abstract List<String> getValuesToBeChecked(HttpServletRequest httpServletRequest);

    protected abstract NodeList getRuleXmlNodeList(Element ruleXmlElementFragment);

    @Override
    public void check(final Element ruleXmlElementFragment, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
        NodeList invalidParameterValueCheck = getRuleXmlNodeList(ruleXmlElementFragment);
        if (invalidParameterValueCheck != null && invalidParameterValueCheck.getLength() == 1) {
            Node invalidParameterValueCheckNode = invalidParameterValueCheck.item(0);
            String description = invalidParameterValueCheckNode.getAttributes().getNamedItem("description").getNodeValue();
            String failureStatusCode = invalidParameterValueCheckNode.getAttributes().getNamedItem("failureStatusCode").getNodeValue();

            NodeList regexNodeList = invalidParameterValueCheckNode.getChildNodes();
            for (int i = 0; i < regexNodeList.getLength(); i++) {
                if (httpServletResponse.isCommitted() == true) {
                    break;
                }
                Node regexNode = regexNodeList.item(i);
                if (regexNode.getNodeType() == Node.ELEMENT_NODE) {
                    String regex = regexNode.getFirstChild().getNodeValue();
                    boolean caseSensitive = Boolean.valueOf(regexNode.getAttributes().getNamedItem("caseSensitive").getNodeValue());
                    boolean multiLine = Boolean.valueOf(regexNode.getAttributes().getNamedItem("multiLine").getNodeValue());
                    boolean dotAll = Boolean.valueOf(regexNode.getAttributes().getNamedItem("dotAll").getNodeValue());

                    Pattern pattern = regexCache.get(regex);
                    if (pattern == null) {
                        int regexFlags = 0;
                        if (caseSensitive == false) {
                            regexFlags = Pattern.CASE_INSENSITIVE;
                        }
                        if (multiLine == true) {
                            if (regexFlags == 0) {
                                regexFlags = Pattern.MULTILINE;
                            } else {
                                regexFlags = regexFlags | Pattern.MULTILINE;
                            }
                        }
                        if (dotAll == true) {
                            if (regexFlags == 0) {
                                regexFlags = Pattern.DOTALL;
                            } else {
                                regexFlags = regexFlags | Pattern.DOTALL;
                            }
                        }
                        final Pattern value = Pattern.compile(regex, regexFlags);
                        pattern = regexCache.putIfAbsent(regex, value);
                        if (pattern == null) {
                            pattern = value;
                        }
                    }

                    List<String> valuesToBeChecked = getValuesToBeChecked(httpServletRequest);
                    for (String valueToBeChecked : valuesToBeChecked) {
                        valueToBeChecked = valueToBeChecked.replaceAll("\0", "");
                        if (pattern.matcher(valueToBeChecked).matches()) {
                            try {
                                if (checkFailureCallback != null) {
                                    checkFailureCallback.onFailure(httpServletRequest, httpServletResponse, new CheckFailureMetadata(invalidParameterValueCheckNode.getNodeName(),
                                            description, regex, null, valueToBeChecked));
                                }
                                if (httpServletResponse.isCommitted() == false) {
                                    httpServletResponse.sendError(Integer.parseInt(failureStatusCode), "Potential malicious value: " + valueToBeChecked);
                                }

                            } catch (NumberFormatException e) {
                                e.printStackTrace();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                }
            }
        }

    }

    @Override
    public void addCheckFailureCallback(final CheckFailureCallback checkFailureCallback) {
        this.checkFailureCallback = checkFailureCallback;
    }

}
